package top.healthylife.gateway.mgr.controller;

import cn.hutool.core.codec.Base64Decoder;
import cn.hutool.core.net.URLDecoder;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import top.healthylife.basics.utils.exception.BusinessException;
import top.healthylife.basics.utils.utils.*;
import top.healthylife.gateway.mgr.component.ApiComponent;
import top.healthylife.gateway.mgr.component.GatewaySaveLogComponent;
import top.healthylife.gateway.mgr.service.GatewayAccountService;
import top.healthylife.gateway.mgr.service.GatewayLogService;
import top.heathylife.gateway.common.cons.SystemCons;
import top.heathylife.gateway.common.po.*;
import top.heathylife.gateway.common.utils.SecretJsonUtils;
import top.heathylife.gateway.common.vo.ApiReqRestVo;

import java.nio.charset.Charset;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @author maoxiaomeng
 */
@Api(tags = "数据网关服务-接口信息")
@Slf4j
@RestController
@RequestMapping("/gateway/interface")
@AllArgsConstructor
public class InterfaceController {
    ApiComponent apiComponent;
    RestTemplate restTemplate;
    GatewayAccountService gatewayAccountService;
    GatewaySaveLogComponent gatewaySaveLogComponent;
    GatewayLogService gatewayLogService;


    @ApiOperation("接口列表-分页")
    @GetMapping("/page")
    public R<Page<ParentApiPo>> interfaces(@RequestParam(required = false) String shortName,
                                           @RequestParam(required = false) String apiName,
                                           @RequestParam(required = false, defaultValue = "1") long current,
                                           @RequestParam(required = false, defaultValue = "10") long size
    ) {
        List<ParentApiPo> allApis = getAllAvailableSystemApis(shortName);
        if (ObjectUtil.isNotEmpty(apiName)) {
            allApis = allApis.stream().filter(e -> e.getApiName().contains(apiName)).collect(Collectors.toList());
        }
        Page<ParentApiPo> page = new Page<>(current, size);
        page.setTotal(allApis.size());
        page.setRecords(PageUtil.startPage(allApis, page.getCurrent(), page.getSize()));
        return R.ok(page);
    }


    @ApiOperation("接口列表-list")
    @GetMapping("/list")
    public R<List<ParentApiPo>> interfacesList(String shortName) {
        if (ObjectUtil.isEmpty(shortName)) {
            //查询所有
            return R.ok(getAllAvailableSystemApis(shortName));
        }
        return R.ok(apiComponent.checkSystemPo(shortName).getApis());
    }


    @ApiOperation("接口的入参和出参的class")
    @GetMapping("/params/cls")
    R<List<String>> getClasses(@RequestParam String shortName, @RequestParam String apiName) {
        ParentApiPo apiPo = apiComponent.checkSystemPo(shortName).getApis().stream()
                .filter(e -> e.getApiName().equals(apiName))
                .findAny().orElseThrow(() -> new BusinessException("API未找到"));
        return R.ok(new ArrayList<String>() {{
            add(apiPo.getCls());
            add(apiPo.getResCls());
        }});
    }


    @ApiOperation("获取接口API的实体cls")
    @GetMapping("/parentFace/cls")
    R<String> getCls(@RequestParam String shortName, @RequestParam String apiName) {
        String cls = apiComponent.checkSystemPo(shortName).getApis().stream().filter(e -> e.getApiName().equals(apiName)).findAny().orElseThrow(() -> new BusinessException("API未找到")).getCls();
        return R.ok(cls);
    }


    @ApiOperation("模糊检索API返回参数")
    @GetMapping("/search")
    public R<?> search(@RequestParam String shortName, @RequestParam String keyword) {
        //根据接口名称,和服务名进行数据返回
        //获取所有的第三方的服务名
        String redisKey = "interface:api:gateway:" + shortName;
        //匹配第方三方在线的服务
        //获取所有在线的服务名
        SystemPo systemPo = Optional.ofNullable(shortName)
                .map(e -> RedisUtil.get(redisKey, SystemPo.class))
                .orElseThrow(() -> new BusinessException("没有此服务"));
        //取出对应的服务名,请求具体的微服务
        return restTemplate.getForObject(String.format("http://%s/search?searchName=%s", systemPo.getApplicationName(), keyword), R.class);
    }


    @ApiOperation("根据接口名获取出参入参")
    @GetMapping("/info")
    @SuppressWarnings("unchecked")
    public R<ApiReqRestVo> apiParams(@RequestParam String apiName, @RequestParam String shortName) {
        //获取服务简称映射的服务名,请求具体的微服务
        SystemPo systemPo = apiComponent.checkSystemPo(shortName);
        String appName = systemPo.getApplicationName();
        return restTemplate.getForObject(String.format("http://%s/api?apiName=%s", appName,
                apiName), R.class);
    }


    @ApiOperation("接口请求参数")
    @GetMapping("/{shortName}/{apiName}")
    public R<List<ApiParameterPo>> getInterfaces(@PathVariable String shortName, @PathVariable String apiName) {
        //直接获取缓存的东西。
        SystemPo systemPo = apiComponent.checkSystemPo(shortName);
        ParentApiPo parentApiPo = systemPo.getApis().stream()
                .filter(e -> e.getApiName().equals(apiName))
                .findAny()
                .orElseThrow(() -> new BusinessException("接口不存在"));
        List<ApiParameterPo> parameters = parentApiPo.getParameters();
        return R.ok(parameters);
    }


    /**
     * 直接调用第三方接口
     *
     * @param reqParams       请求接口需要的入参 格式为 map对象 转换为json,然后使用secret作为加密秘钥 用Aes加密,在使用Base64格式编码放在请求体中
     * @param apiName         接口名 如果包含中文,需要URL_ENCODE
     * @param applicationName 调用方名称,如果包含中文 需要URL_ENCODE
     * @param secret          secret 秘钥 base64编码后放入链接
     * @return 三方接口返回的数据
     */
    @ApiOperation("调用接口服务网关(外部)")

    @PostMapping("/invoke/{secret}")
    @SuppressWarnings("unchecked")
    public R<?> invokeApiData(@RequestHeader("apiName") String apiName,
                              @RequestHeader("applicationName") String applicationName,
                              @RequestHeader(value = "periodValidity", required = false) Long periodValidity,
                              @RequestBody Map<String, Object> reqParams, @PathVariable String secret) {

        String apiNameDecode;
        String applicationNameDecode;
        String reallySecret;
        //1.base64解码秘钥 拿到apiName,secret 的明文
        try {
            applicationNameDecode = new String(Base64Decoder.decode(applicationName));
            apiNameDecode = new String(Base64Decoder.decode(apiName));
            byte[] secretDecode = Base64Decoder.decode(secret);
            reallySecret = new String(secretDecode);
        } catch (Exception e) {
            throw new BusinessException("请求参数解码异常");
        }
        //2.根据secret查询帐号信息,理论上只允许存在一个,租户唯一;
        GatewayAccountPo secretPo;
        try {
            secretPo = gatewayAccountService.getOne(Wrappers.query(new GatewayAccountPo().setSecret(reallySecret)));
            if (secretPo == null) {
                return R.failed(10001, "未查询到该账号信息,请检查秘钥");
            }
        } catch (Exception e) {
            String message = StrUtil.format("查询数据网关账户信息异常,异常信息:{}(Maybe caused by duty data),请联系管理员", e.getMessage());
            log.error(message);
            return R.failed(10001, message);
        }
        applicationNameDecode = StrUtil.format("http调用:{}", applicationNameDecode);
        //直接请求接口网关
        try {
            R<?> r = results(apiNameDecode, reqParams, reallySecret, periodValidity, applicationNameDecode);
            if (r.getCode() == 0) {
                Object data1 = r.getData();
                if (data1 instanceof LinkedHashMap) {
                    log.info("r.getData() 结果类型为 LinkedHashMap");
                    LinkedHashMap<String, Object> linkedHashMap = (LinkedHashMap<String, Object>) data1;
                    linkedHashMap.remove("cls");
                } else if (data1 instanceof JSONObject) {
                    log.info("r.getData() 结果类型为 JSONObject");
                    JSONObject data = (JSONObject) data1;
                    data.remove("cls");
                } else {
                    Class<?> aClass = data1.getClass();
                    log.info("r.getData() 结果类型为 {}", aClass.getSimpleName());
                }
            }
            return r;
        } catch (Exception e) {
            String message = StrUtil.format("请求数据网关异常,异常信息:{}", e.getMessage());
            return R.failed(10002, message);
        }

    }

    //@Log
    @ApiOperation("调用接口服务网关(内部)")
    @PostMapping("/results/{apiName}")
    public R<?> results(
            @PathVariable String apiName,
            @RequestBody Map<String, Object> data,
            @RequestHeader("secret") String secret,
            @RequestHeader(value = "periodValidity", required = false) Long periodValidity,
            @RequestHeader(value = "applicationName", required = false) String applicationName) {
        long l1 = System.currentTimeMillis();
        apiName = URLDecoder.decode(apiName, Charset.defaultCharset());
        applicationName = URLDecoder.decode(applicationName, Charset.defaultCharset());
        //对数据进行加密
        String string = JSONObject.toJSONString(data);
        String md5 = SecureUtil.md5(string + apiName + secret);
        //检查是否存在这个secret帐号是否存在
        GatewayAccountPo gatewayAccountPo = apiComponent.checkSecret(secret);
        String shortName = gatewayAccountPo.getShortName();
        log.info("调用接口服务网关的请求参数:接口名,{},平台名称:{},帐号,{},有效时间,{},md5值,{}", apiName, shortName, secret, periodValidity, md5);
        //初始化日志信息
        GatewayLogPo gatewayLogPo = GatewayLogPo.builder()
                .md5(md5)
                //流水号
                .groupNo(IdGenerator.getId())
                .no(IdGenerator.getId())
                //调用方服务名
                .applicationName(applicationName)
                .requestData(string)
                .originReqData(null)
                .returnData(null)
                //非命中缓存
                //接口名称
                .apiName(apiName)
                .secret(secret)
                //接口简称
                .shortName(shortName)
                //开始时间
                .createTime(LocalDateTime.now())
                //ip
                .ip(IpUtil.getIp())
                .build();
        //如果未传入有效时间,则获取各个数据源默认的有效时间
        if (periodValidity != null) {
            gatewayLogPo.setPeriodValidity(periodValidity + "A");
        } else {
            periodValidity = getDefaultPeriodValidity(shortName, gatewayLogPo);
        }
        //判断有效时间
        if (periodValidity >= 0) {
            //检查缓存是否存在此数据
            Object cache = apiComponent.cache(periodValidity, shortName, apiName, md5);
            //如果缓存中没有数据,则走真实接口
            if (Optional.ofNullable(cache).isPresent()) {
                log.info("命中缓存数据");
                gatewayLogPo.setHitCache(true);
                gatewayLogPo.setReturnData(JSONObject.toJSONString(cache));
                //请求结果成功
                gatewayLogPo.setStatus(true);
                gatewayLogPo.setFinishTime(LocalDateTime.now());
                gatewaySaveLogComponent.saveLog(gatewayLogPo);
                long l3 = System.currentTimeMillis();
                log.info("网关耗时" + (l3 - l1));
                //触发日志保存
                return JSONObject.parseObject(JSONObject.toJSONString(cache), R.class);
            }
            log.info("缓存中没有数据，走真实接口");
        }
        log.info("缓存有效时间<0,实时查询三方接口数据");
        //检查下游服务是否存在，如果存在，直接返回下游系统基础信息
        SystemPo systemPo = apiComponent.checkSystemPo(shortName);

        //请求具体的微服务
        String secretJson = gatewayAccountPo.getSecretJson();
        Object o = apiComponent.callService(secret, apiName, SecretJsonUtils.encryptBase64(secretJson), systemPo.getApplicationName(), data, gatewayLogPo);
        if (Optional.ofNullable(o).isPresent()) {
            return R.ok(o);
        } else {
            R<?> resultDtoR;
            String exception = SystemThreadLocal.get("exception");
            SystemThreadLocal.remove("exception");
            if (exception != null) {
                resultDtoR = R.failed(exception);
            } else {
                resultDtoR = R.failed("接口调用失败,请联系管理员");
            }
            return resultDtoR.setCode(5001);
        }
    }


    @ApiOperation("根据服务名获取缓存中最新的一条数据")
    @PostMapping("/cache/{shortName}/{apiName}")
    public R<?> cache(@PathVariable String shortName, @PathVariable String apiName) {
        //查询缓存中最新的那一条数据
        Object cache = apiComponent.cache(0L, shortName, apiName, null);
        if (Optional.ofNullable(cache).isPresent()) {
            log.info("命中缓存数据");
            //触发日志保存
            return R.ok(cache);
        }
        return R.failed("没有找到缓存数据");
    }

    /**
     * 获取所有在线系统的接口列表
     *
     * @param shortName 指定系统 可选的
     * @return 接口列表
     */
    private List<ParentApiPo> getAllAvailableSystemApis(String shortName) {
        List<SystemPo> list = RedisUtil.list(SystemCons.platformKey(), SystemPo.class);
        //过滤存活的项目
        Predicate<SystemPo> filter = e -> RedisUtil.exists(SystemCons.systemHealthKey(e.getShortName()));
        List<SystemPo> collect = list.stream().filter(filter)
                .map(e -> apiComponent.checkSystemPo(e.getShortName())).collect(Collectors.toList());
        return collect.stream()
                .filter(e -> ObjectUtil.isEmpty(shortName) || e.getShortName().equals(shortName))
                .flatMap(e -> e.getApis().stream())
                .collect(Collectors.toList());
    }

    /**
     * 获取各个数据源的默认缓存时间
     *
     * @param shortName 数据源简称
     * @return 默认缓存时间
     */
    public static Long getDefaultPeriodValidity(String shortName, GatewayLogPo gatewayLogPo) {
        Long aLong;
        List<SystemPo> list = RedisUtil.list(SystemCons.platformKey(), SystemPo.class);
        SystemPo systemPo =
                list.stream().filter(e -> e.getShortName().equals(shortName)).findAny().orElse(null);
        if (systemPo == null) {
            //默认缓存时效性一小时
            log.info("当前请求数据源:{},尚未预置默认有效缓存时间,采取默认设置:{}秒", shortName, 3600L);
            aLong = 3600L;
            gatewayLogPo.setPeriodValidity(aLong + "C");
            return aLong;
        }
        aLong = systemPo.getPeriodValidity();
        gatewayLogPo.setPeriodValidity(aLong + "B");
        log.info("当前请求数据源:{},未传递默认有效缓存时间,已预置默认有效缓存时间:{}秒", shortName, aLong);
        return aLong;
    }

}
